using System;
using System.Globalization;


namespace BizElements.BusinessLayer
{
    /// <summary>
    /// Builds commonly used <b>INamedValueSet</b> objects which represent date intervals.
    /// </summary>
    public sealed class DateIntervalBuilder
    {
        #region Date constants and configuration.

        const int NumberOfDaysInAWeek = 7;        
        static readonly TimeSpan OneDay = new TimeSpan(1, 0, 0, 0);
        static readonly TimeSpan TwoDays = new TimeSpan(2, 0, 0, 0);
        static readonly TimeSpan OneWeek = new TimeSpan(NumberOfDaysInAWeek, 0, 0, 0);
        static readonly TimeSpan TwoWeeks = new TimeSpan(2 * NumberOfDaysInAWeek, 0, 0, 0);

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of <b>DateIntervalBuilder</b> class.
        /// </summary>
        public DateIntervalBuilder()
        { }

        /// <summary>
        /// Initializes a new instance of <b>DateIntervalBuilder</b> class.
        /// </summary>
        /// <param name="getCurrentDateTimeMethod">A method which provides current time. If <b>null</b>
        /// <see cref="Time.Now"/> property will be used.</param>
        public DateIntervalBuilder(DateTimeProvider getCurrentDateTimeMethod)
        {
            this.GetCurrentDateTime = getCurrentDateTimeMethod;
        }

        /// <summary>
        /// Initializes a new instance of <b>DateIntervalBuilder</b> class.
        /// </summary>
        /// <param name="getCurrentDateTimeMethod">A method which provides current time. If <b>null</b>
        /// <param name="endOfDayMargin">Margin which determines when a date interval ends. If <b>null</b>
        /// a default margin of 1 second is used. Ie. a date interval ends on the last day of the interval
        /// at 23:59:59 hours.</param>
        /// <see cref="Time.Now"/> property will be used.</param>
        public DateIntervalBuilder(DateTimeProvider getCurrentDateTimeMethod, TimeSpan? endOfDayMargin)
        {
            this.GetCurrentDateTime = getCurrentDateTimeMethod;
            if (endOfDayMargin != null)
                this.endOfDayMargin = endOfDayMargin.Value;
        }

        #endregion

        #region EndOfDayMargin.

        static readonly TimeSpan defaultEndOfDayMargin = new TimeSpan(0, 0, 1);
        TimeSpan endOfDayMargin = defaultEndOfDayMargin;

        /// <summary>
        /// Gets or sets the margin which determines when a date interval ends.
        /// </summary>
        /// <remarks>Default value is 1 second. Ie. a date interval ends on the last day of the interval
        /// at 23:59:59 hours. Setting this values to an unreasonable big TimeSpan may cause errors and
        /// yield unexpected results.</remarks>
        public TimeSpan EndOfDayMargin
        {
            get { return this.endOfDayMargin; }
            set { this.endOfDayMargin = value; }
        }

        #endregion

        #region GetCurrentDateTime delegate.

        /// <summary>
        /// Represents a method that provides a DateTime value.
        /// </summary>
        /// <returns>A DateTime value.</returns>
        public delegate DateTime DateTimeProvider();

        /// <summary>
        /// The method which returns the current time used by all builder methods.
        /// </summary>
        /// <remarks><para>If <b>null</b>, the <see cref="Time.Now"/> property which returns the current 
        /// system time is used.</para>
        /// <para>Usefull when you want to build intervals for other time zones, intervals for past or future etc.
        /// </para></remarks>
        public DateTimeProvider GetCurrentDateTime;

        private DateTime GetTime()
        {
            if (this.GetCurrentDateTime != null)
                return this.GetCurrentDateTime();
            else
                return Time.Now;
        }

        #endregion

        #region Days.

        /// <summary>
        /// Creates a DateTime interval for today.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> Today(string name)
        {
            DateTime dayBegin = GetTime().Date;
            DateTime dayEnd = dayBegin + OneDay - EndOfDayMargin;            
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval for tomorrow.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> Tomorrow(string name)
        {
            DateTime dayBegin = GetTime().Date + OneDay;
            DateTime dayEnd = dayBegin + OneDay - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval for yesterday.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> Yesterday(string name)
        {
            DateTime dayBegin = GetTime().Date - OneDay;
            DateTime dayEnd = dayBegin + OneDay - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval for day before yesterday.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> TwoDaysAgo(string name)
        {
            DateTime dayBegin = GetTime().Date - TwoDays;
            DateTime dayEnd = dayBegin + OneDay - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of days in the past and optionally the current day.
        /// </summary>
        /// <param name="dayCount">Number of days before today. At least 1.</param>
        /// <param name="includeEntireCurrentDay">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>dayCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> LastXDays(int dayCount, bool includeEntireCurrentDay, string name)
        {
            if (dayCount < 1)
                throw new ArgumentOutOfRangeException("dayCount", dayCount, "Day count must be 1 or greater.");

            DateTime today = GetTime().Date;
            DateTime dayBegin = today.AddDays(-dayCount);
            DateTime dayEnd;
            if (includeEntireCurrentDay)
                dayEnd = today + OneDay - EndOfDayMargin;
            else
                dayEnd = today - EndOfDayMargin;

            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of days in the future and optionally the current day.
        /// </summary>
        /// <param name="dayCount">Number of days after today. At least 1.</param>
        /// <param name="includeEntireCurrentDay">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>dayCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> NextXDays(int dayCount, bool includeEntireCurrentDay, string name)
        {
            if (dayCount < 1)
                throw new ArgumentOutOfRangeException("dayCount", dayCount, "Day count must be 1 or greater.");

            DateTime today = GetTime().Date;
            DateTime dayBegin = (includeEntireCurrentDay) ? today : today + OneDay;
            DateTime dayEnd = today.AddDays(dayCount) + OneDay - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        #endregion

        #region Weeks.

        private static DateTime GetWeekStartForDate(CultureInfo culture, DateTime date)
        {
            int firstDayOrdinal = (int)culture.DateTimeFormat.FirstDayOfWeek;
            int givenDayOrdinal = (int)date.DayOfWeek;
            int daysToSubstractFromGivenDateToGetWeekStart = givenDayOrdinal - firstDayOrdinal;
            if (daysToSubstractFromGivenDateToGetWeekStart < 0)
                daysToSubstractFromGivenDateToGetWeekStart += NumberOfDaysInAWeek;

            DateTime weekStart = date.Date.AddDays(-daysToSubstractFromGivenDateToGetWeekStart);
            return weekStart;
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the current week.
        /// </summary>
        /// <param name="culture">Culture which determines the first day of the week.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentNullException">ArgumentNullException is generated if the culture is not provided.</exception>
        public NamedValueSet<DateTime> CurrentWeek(CultureInfo culture, string name)
        {
            if (culture == null)
                throw new ArgumentNullException("culture", "Culture is required to determine the first day of the week.");

            DateTime dayBegin = GetWeekStartForDate(culture, GetTime());
            DateTime dayEnd = dayBegin + OneWeek - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the next week.
        /// </summary>
        /// <param name="culture">Culture which determines the first day of the week.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentNullException">ArgumentNullException is generated if the culture is not provided.</exception>
        public NamedValueSet<DateTime> NextWeek(CultureInfo culture, string name)
        {
            if (culture == null)
                throw new ArgumentNullException("culture", "Culture is required to determine the first day of the week.");

            DateTime dayBegin = GetWeekStartForDate(culture, GetTime()) + OneWeek;
            DateTime dayEnd = dayBegin + OneWeek - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the last week.
        /// </summary>
        /// <param name="culture">Culture which determines the first day of the week.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentNullException">ArgumentNullException is generated if the culture is not provided.</exception>
        public NamedValueSet<DateTime> LastWeek(CultureInfo culture, string name)
        {
            if (culture == null)
                throw new ArgumentNullException("culture", "Culture is required to determine the first day of the week.");

            DateTime dayBegin = GetWeekStartForDate(culture, GetTime()) - OneWeek;
            DateTime dayEnd = dayBegin + OneWeek - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the week before the last week.
        /// </summary>
        /// <param name="culture">Culture which determines the first day of the week.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentNullException">ArgumentNullException is generated if the culture is not provided.</exception>
        public NamedValueSet<DateTime> TwoWeeksAgo(CultureInfo culture, string name)
        {
            if (culture == null)
                throw new ArgumentNullException("culture", "Culture is required to determine the first day of the week.");

            DateTime dayBegin = GetWeekStartForDate(culture, GetTime()) - TwoWeeks;
            DateTime dayEnd = dayBegin + OneWeek - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of weeks in the past and optionally the current week.
        /// </summary>
        /// <param name="culture">Culture which determines the first day of the week.</param>
        /// <param name="weekCount">Number of weeks before the current week. At least 1.</param>
        /// <param name="includeEntireCurrentWeek">Specifies whether the current week is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentNullException">ArgumentNullException is generated if the culture is not provided.</exception>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>weekCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> LastXWeeks(CultureInfo culture, int weekCount, bool includeEntireCurrentWeek, string name)
        {
            if (culture == null)
                throw new ArgumentNullException("culture", "Culture is required to determine the first day of the week.");

            if (weekCount < 1)
                throw new ArgumentOutOfRangeException("weekCount", weekCount, "Week count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currWeekStart = GetWeekStartForDate(culture, now);
            DateTime dayBegin = currWeekStart.AddDays(-weekCount * NumberOfDaysInAWeek);
            DateTime dayEnd;
            if (includeEntireCurrentWeek)
            {
                DateTime currWeekEnd = currWeekStart + OneWeek - EndOfDayMargin;
                dayEnd = currWeekEnd;
            }
            else
            {
                DateTime lastWeekEnd = currWeekStart - EndOfDayMargin;
                dayEnd = lastWeekEnd;
            }

            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of weeks in the past including all days of the current week till today.
        /// </summary>
        /// <param name="culture">Culture which determines the first day of the week.</param>
        /// <param name="weekCount">Number of weeks before the current week. At least 1.</param>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentNullException">ArgumentNullException is generated if the culture is not provided.</exception>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>weekCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> LastXWeeksTillToday(CultureInfo culture, int weekCount, bool includeToday, string name)
        {
            if (culture == null)
                throw new ArgumentNullException("culture", "Culture is required to determine the first day of the week.");

            if (weekCount < 1)
                throw new ArgumentOutOfRangeException("weekCount", weekCount, "Week count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currWeekStart = GetWeekStartForDate(culture, now);
            DateTime dayBegin = currWeekStart.AddDays(-weekCount * NumberOfDaysInAWeek);
            DateTime dayEnd = (includeToday) ? now.Date + OneDay - EndOfDayMargin : now.Date - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of weeks in the future and optionally the current week.
        /// </summary>
        /// <param name="culture">Culture which determines the first day of the week.</param>
        /// <param name="weekCount">Number of weeks after the current week. At least 1.</param>
        /// <param name="includeEntireCurrentWeek">Specifies whether the current week is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentNullException">ArgumentNullException is generated if the culture is not provided.</exception>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>weekCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> NextXWeeks(CultureInfo culture, int weekCount, bool includeEntireCurrentWeek, string name)
        {
            if (culture == null)
                throw new ArgumentNullException("culture", "Culture is required to determine the first day of the week.");

            if (weekCount < 1)
                throw new ArgumentOutOfRangeException("weekCount", weekCount, "Week count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currWeekStart = GetWeekStartForDate(culture, now);
            DateTime nextWeekStart = currWeekStart + OneWeek;
            DateTime dayBegin = (includeEntireCurrentWeek) ? currWeekStart : nextWeekStart;
            DateTime dayEnd = nextWeekStart.AddDays(weekCount * NumberOfDaysInAWeek) - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of weeks in the future including today and all days of the current week after today.
        /// </summary>
        /// <param name="culture">Culture which determines the first day of the week.</param>
        /// <param name="weekCount">Number of weeks after the current week. At least 1.</param>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentNullException">ArgumentNullException is generated if the culture is not provided.</exception>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>weekCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> NextXWeeksFromToday(CultureInfo culture, int weekCount, bool includeToday, string name)
        {
            if (culture == null)
                throw new ArgumentNullException("culture", "Culture is required to determine the first day of the week.");

            if (weekCount < 1)
                throw new ArgumentOutOfRangeException("weekCount", weekCount, "Week count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currWeekStart = GetWeekStartForDate(culture, now);
            DateTime nextWeekStart = currWeekStart + OneWeek;
            DateTime dayBegin = (includeToday) ? now.Date : now.Date + OneDay;
            DateTime dayEnd = nextWeekStart.AddDays(weekCount * NumberOfDaysInAWeek) - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes all previous days of the current week and optionally the current day.
        /// </summary>
        /// <param name="culture">Culture which determines the first day of the week.</param>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentNullException">ArgumentNullException is generated if the culture is not provided.</exception>
        public NamedValueSet<DateTime> EarlierInTheCurrentWeek(CultureInfo culture, bool includeToday, string name)
        {
            if (culture == null)
                throw new ArgumentNullException("culture", "Culture is required to determine the first day of the week.");

            DateTime today = GetTime().Date;
            DateTime currWeekStart = GetWeekStartForDate(culture, today);
            DateTime dayBegin = currWeekStart;
            DateTime dayEnd = (includeToday) ? today + OneDay - EndOfDayMargin : today - EndOfDayMargin;
            
            if (dayEnd < dayBegin)
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayBegin, name);
            else                
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes all future days of the current week and optionally the current day.
        /// </summary>
        /// <param name="culture">Culture which determines the first day of the week.</param>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentNullException">ArgumentNullException is generated if the culture is not provided.</exception>
        public NamedValueSet<DateTime> LaterInTheCurrentWeek(CultureInfo culture, bool includeToday, string name)
        {
            if (culture == null)
                throw new ArgumentNullException("culture", "Culture is required to determine the first day of the week.");

            DateTime today = GetTime().Date;
            DateTime nextWeekStart = GetWeekStartForDate(culture, today) + OneWeek;
            DateTime dayBegin = (includeToday) ? today : today + OneDay;
            DateTime dayEnd = nextWeekStart - EndOfDayMargin;

            if (dayEnd < dayBegin)
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayBegin, name);
            else
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        #endregion

        #region Months.

        private DateTime GetMonthStartForDate(DateTime date)
        {
            return new DateTime(date.Year, date.Month, 1);
        }

        private DateTime GetMonthEndForDate(DateTime date)
        {
            DateTime monthStart = GetMonthStartForDate(date);
            return monthStart.AddMonths(1) - EndOfDayMargin;
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the current month.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> CurrentMonth(string name)
        {
            DateTime dayBegin = GetMonthStartForDate(GetTime());
            DateTime dayEnd = GetMonthEndForDate(dayBegin);
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the next month.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> NextMonth(string name)
        {
            DateTime dayBegin = GetMonthStartForDate(GetTime().AddMonths(1));
            DateTime dayEnd = GetMonthEndForDate(dayBegin);
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the last month.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> LastMonth(string name)
        {
            DateTime dayBegin = GetMonthStartForDate(GetTime().AddMonths(-1));
            DateTime dayEnd = GetMonthEndForDate(dayBegin);
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the month before the last month.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> TwoMonthsAgo(string name)
        {
            DateTime dayBegin = GetMonthStartForDate(GetTime().AddMonths(-2));
            DateTime dayEnd = GetMonthEndForDate(dayBegin);
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of months in the past and optionally the current month.
        /// </summary>
        /// <param name="monthCount">Number of months before the current month. At least 1.</param>
        /// <param name="includeEntireCurrentMonth">Specifies whether the current month is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>monthCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> LastXMonths(int monthCount, bool includeEntireCurrentMonth, string name)
        {
            if (monthCount < 1)
                throw new ArgumentOutOfRangeException("monthCount", monthCount, "Month count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currMonthStart = GetMonthStartForDate(now);
            DateTime dayBegin = currMonthStart.AddMonths(-monthCount);
            DateTime dayEnd;
            if (includeEntireCurrentMonth)
            {
                dayEnd = GetMonthEndForDate(now);
            }
            else
            {
                DateTime lastMonthEnd = currMonthStart - EndOfDayMargin;
                dayEnd = lastMonthEnd;
            }

            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of months in the past including all days of the current month till today.
        /// </summary>
        /// <param name="monthCount">Number of months before the current month. At least 1.</param>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>monthCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> LastXMonthsTillToday(int monthCount, bool includeToday, string name)
        {
            if (monthCount < 1)
                throw new ArgumentOutOfRangeException("monthCount", monthCount, "Month count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currMonthStart = GetMonthStartForDate(now);
            DateTime dayBegin = currMonthStart.AddMonths(-monthCount);
            DateTime dayEnd = (includeToday) ? now.Date + OneDay - EndOfDayMargin : now.Date - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of months in the future and optionally the current month.
        /// </summary>
        /// <param name="monthCount">Number of months after the current month. At least 1.</param>
        /// <param name="includeEntireCurrentMonth">Specifies whether the current month is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>monthCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> NextXMonths(int monthCount, bool includeEntireCurrentMonth, string name)
        {
            if (monthCount < 1)
                throw new ArgumentOutOfRangeException("monthCount", monthCount, "Month count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currMonthStart = GetMonthStartForDate(now);
            DateTime nextMonthStart = currMonthStart.AddMonths(1);
            DateTime dayBegin = (includeEntireCurrentMonth) ? currMonthStart : nextMonthStart;
            DateTime dayEnd = nextMonthStart.AddMonths(monthCount) - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of months in the future including today and all days of the current month after today.
        /// </summary>
        /// <param name="monthCount">Number of months after the current month. At least 1.</param>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>monthCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> NextXMonthsFromToday(int monthCount, bool includeToday, string name)
        {
            if (monthCount < 1)
                throw new ArgumentOutOfRangeException("monthCount", monthCount, "Month count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currMonthStart = GetMonthStartForDate(now);
            DateTime nextMonthStart = currMonthStart.AddMonths(1);
            DateTime dayBegin = (includeToday) ? now.Date : now.Date + OneDay;
            DateTime dayEnd = nextMonthStart.AddMonths(monthCount) - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes all previous days of the current month and optionally the current day.
        /// </summary>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> EarlierInTheCurrentMonth(bool includeToday, string name)
        {
            DateTime today = GetTime().Date;
            DateTime dayBegin = GetMonthStartForDate(today);
            DateTime dayEnd = (includeToday) ? today + OneDay - EndOfDayMargin : today - EndOfDayMargin;

            if (dayEnd < dayBegin)
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayBegin, name);
            else
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes all future days of the current month and optionally the current day.
        /// </summary>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> LaterInTheCurrentMonth(bool includeToday, string name)
        {
            DateTime today = GetTime().Date;
            DateTime dayBegin = (includeToday) ? today : today + OneDay;
            DateTime dayEnd = GetMonthEndForDate(today);

            if (dayEnd < dayBegin)
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayBegin, name);
            else
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        #endregion

        #region Years.

        private DateTime GetYearStartForDate(DateTime date)
        {
            return new DateTime(date.Year, 1, 1);
        }

        private DateTime GetYearEndForDate(DateTime date)
        {
            DateTime yearStart = GetYearStartForDate(date);
            return yearStart.AddYears(1) - EndOfDayMargin;
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the current year.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> CurrentYear(string name)
        {
            DateTime dayBegin = GetYearStartForDate(GetTime());
            DateTime dayEnd = GetYearEndForDate(dayBegin);
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the next year.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> NextYear(string name)
        {
            DateTime dayBegin = GetYearStartForDate(GetTime().AddYears(1));
            DateTime dayEnd = GetYearEndForDate(dayBegin);
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the last year.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> LastYear(string name)
        {
            DateTime dayBegin = GetYearStartForDate(GetTime().AddYears(-1));
            DateTime dayEnd = GetYearEndForDate(dayBegin);
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which contains all days of the year before the last year.
        /// </summary>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> TwoYearsAgo(string name)
        {
            DateTime dayBegin = GetYearStartForDate(GetTime().AddYears(-2));
            DateTime dayEnd = GetYearEndForDate(dayBegin);
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of years in the past and optionally the current year.
        /// </summary>
        /// <param name="yearCount">Number of years before the current year. At least 1.</param>
        /// <param name="includeEntireCurrentYear">Specifies whether the current year is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>yearCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> LastXYears(int yearCount, bool includeEntireCurrentYear, string name)
        {
            if (yearCount < 1)
                throw new ArgumentOutOfRangeException("yearCount", yearCount, "Year count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currYearStart = GetYearStartForDate(now);
            DateTime dayBegin = currYearStart.AddYears(-yearCount);
            DateTime dayEnd;
            if (includeEntireCurrentYear)
            {
                dayEnd = GetYearEndForDate(now);
            }
            else
            {
                DateTime lastYearEnd = currYearStart - EndOfDayMargin;
                dayEnd = lastYearEnd;
            }

            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of years in the past including all days of the current year till today.
        /// </summary>
        /// <param name="yearCount">Number of years before the current year. At least 1.</param>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>yearCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> LastXYearsTillToday(int yearCount, bool includeToday, string name)
        {
            if (yearCount < 1)
                throw new ArgumentOutOfRangeException("yearCount", yearCount, "Year count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currYearStart = GetYearStartForDate(now);
            DateTime dayBegin = currYearStart.AddYears(-yearCount);
            DateTime dayEnd = (includeToday) ? now.Date + OneDay - EndOfDayMargin : now.Date - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of years in the future and optionally the current year.
        /// </summary>
        /// <param name="yearCount">Number of years after the current year. At least 1.</param>
        /// <param name="includeEntireCurrentYear">Specifies whether the current year is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>yearCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> NextXYears(int yearCount, bool includeEntireCurrentYear, string name)
        {
            if (yearCount < 1)
                throw new ArgumentOutOfRangeException("yearCount", yearCount, "Year count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currYearStart = GetYearStartForDate(now);
            DateTime nextYearStart = currYearStart.AddYears(1);
            DateTime dayBegin = (includeEntireCurrentYear) ? currYearStart : nextYearStart;
            DateTime dayEnd = nextYearStart.AddYears(yearCount) - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes the specified number of years in the future including today and all days of the current year after today.
        /// </summary>
        /// <param name="yearCount">Number of years after the current year. At least 1.</param>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        /// <exception cref="ArgumentOutOfRangeException">ArgumentOutOfRangeException is generated if <b>yearCount</b> is less than 1.</exception>
        public NamedValueSet<DateTime> NextXYearsFromToday(int yearCount, bool includeToday, string name)
        {
            if (yearCount < 1)
                throw new ArgumentOutOfRangeException("yearCount", yearCount, "Year count must be 1 or greater.");

            DateTime now = GetTime();
            DateTime currYearStart = GetYearStartForDate(now);
            DateTime nextYearStart = currYearStart.AddYears(1);
            DateTime dayBegin = (includeToday) ? now.Date : now.Date + OneDay;
            DateTime dayEnd = nextYearStart.AddYears(yearCount) - EndOfDayMargin;
            return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes all previous days of the current year and optionally the current day.
        /// </summary>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> EarlierInTheCurrentYear(bool includeToday, string name)
        {
            DateTime today = GetTime().Date;
            DateTime dayBegin = GetYearStartForDate(today);
            DateTime dayEnd = (includeToday) ? today + OneDay - EndOfDayMargin : today - EndOfDayMargin;

            if (dayEnd < dayBegin)
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayBegin, name);
            else
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        /// <summary>
        /// Creates a DateTime interval which includes all future days of the current year and optionally the current day.
        /// </summary>
        /// <param name="includeToday">Specifies whether the current day is included in the interval.</param>
        /// <param name="name">Interval name. Nullable.</param>
        /// <returns>NamedValueSet whose BeginValue and EndValue properties contain DateTime values.</returns>
        public NamedValueSet<DateTime> LaterInTheCurrentYear(bool includeToday, string name)
        {
            DateTime today = GetTime().Date;
            DateTime dayBegin = (includeToday) ? today : today + OneDay;
            DateTime dayEnd = GetYearEndForDate(today);

            if (dayEnd < dayBegin)
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayBegin, name);
            else
                return NamedValueSet<DateTime>.NewInterval(dayBegin, dayEnd, name);
        }

        #endregion
    }
}
